#pragma once

#include "angle.h"
#include "mat.h"

namespace sfc::math::geo {

struct Affine {
  Mat2d _mat = Mat2d::eye();
  Vec2d _vec = {0, 0};

 public:
  [[nodiscard]] auto zoom(f64 k) const -> Affine {
    const auto mat = Mat2d{Vec2d{k, 0}, Vec2d{0, k}};
    const auto res = Affine{mat * _mat, mat * _vec};
    return res;
  }

  [[nodiscard]] auto scale(f64 kx, f64 ky) const -> Affine {
    const auto mat = Mat2d{Vec2d{kx, 0}, Vec2d{0, ky}};
    const auto res = Affine{mat * _mat, mat * _vec};
    return res;
  }

  [[nodiscard]] auto reflect_over_x() const -> Affine {
    return this->scale(+1, -1);
  }

  [[nodiscard]] auto reflect_over_y() const -> Affine {
    return this->scale(-1, +1);
  }

  [[nodiscard]] auto translate(const Vec2d& vec) const -> Affine {
    const auto res = Affine{_mat, vec + _vec};
    return res;
  }

  [[nodiscard]] auto rotate(const Vec2d& vec) const -> Affine {
    const auto mat = Mat2d{Vec2{vec[0], -vec[1]}, {vec[1], vec[0]}};
    const auto res = Affine{mat * _mat, mat * _vec};
    return res;
  }

  [[nodiscard]] auto rotate(const Angle& angle) const -> Affine {
    const auto vec = angle.to_vec();
    return this->rotate(vec);
  }

  [[nodiscard]] auto inv() const -> Affine {
    const auto mat = _mat.inv();
    const auto vec = mat * -_vec;
    return {mat, vec};
  }

  template <class T>
  auto operator()(const T& t) const -> T {
    if constexpr (requires { t.transform(*this); }) {
      return t.transform(*this);
    } else {
      return this->trans_imp(t);
    }
  }

 private:
  [[sfc_inline]] auto trans_imp(const Vec2d& vec) const -> Vec2d {
    return _mat * vec;
  }

  [[sfc_inline]] auto trans_imp(const Point& p) const -> Point {
    return Point::from_vec(_mat * p.as_vec()) + _vec;
  }
};

}  // namespace sfc::math::geo
